/**
 * I metodi di questa classe servono per istanziare una griglia.
 * La griglia è implemetata come una matrice di ListaLinked
 *
 * @author Matteo Cuccato
 * @author Nicola Girardi
 */
package stage;

import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LinearRing;
import java.util.Vector;

public class SpatialGrid {

    private double x_i, x_f, y_i, y_f, width;
    private int row, column, n_element, treshold = 6;
    private ListaLinked[][] grid;
    private ResultDistribution rd;
    private boolean empty;

    /**
     * Metodo costrutture
     *
     * @param int row numero di righe della matrice
     * @param int column numero delle righe
     * @param double width dimensione interna di ogni cella
     *
     */
    public SpatialGrid(int row, int column, double width) {

        this.width = width;
        this.row = row;
        this.column = column;
        x_i = 0.0;
        y_i = 0.0;
        x_f = column * width;
        y_f = row * width;
        empty = true;
        n_element = 0;


        // Creazione griglia
        grid = new ListaLinked[this.row][this.column];
        rd = new ResultDistribution();

        // Inizializzazione
        for (int i = 0; i < this.row; i++) {
            for (int j = 0; j < this.column; j++) {
                grid[i][j] = new ListaLinked();
            }//end for j
        }//end for i
    }//fine costruttore

    /**
     * Costruttore alternativo che permette di specificare esplicitamente
     * le dimensioni totali della SpatialGrid
     *
     * @param x_i Double ascissa di origine della SpatialGrid
     * @param x_f Double ordinata di origine della SpatialGrid
     * @param y_i Double ascissa di fine della SpatialGrid
     * @param y_f Double ordinata di fine della SpatialGrid
     * @param width Double dimensione interna di una cella
     */
    public SpatialGrid(double xi, double xf, double yi, double yf, double width) {

        this.width = width;
        empty = true;

        if ((int) (yi % width) != 0) {
            x_i = (int) (xi / width) * width;
            x_f = (int) (xf / width + 1) * width;
            y_i = (int) (yi / width) * width;
            y_f = (int) (yf / width + 1) * width;
        } else {
            x_i = xi;
            x_f = xf;
            y_i = yi;
            y_f = yf;
        }

        column = (int) (this.x_f - this.x_i) / (int) width;
        row = (int) (this.y_f - this.y_i) / (int) width;
        n_element = 0;

        // Creazione griglia
        grid = new ListaLinked[row][column];
        rd = new ResultDistribution();

        // Inizializzazione
        for (int i = 0; i < row; i++) {
            for (int j = 0; j < column; j++) {
                grid[i][j] = new ListaLinked();
            }//end for j
        }//end for i
    }//fine costruttore

    /**
     * Permette di inserire un nuovo oggetto nella SpatialGrid
     *
     * @param int row riga in cui inserire l'oggetto
     * @param int column colonna in cui inserire l'oggetto
     * @param GeometryContainer gc oggetto da inserire nella lista
     * @throws IndexOutOfBoundsException Se row o column non sono validi
     */
    public void insert(int row, int column, Object gc) throws IndexOutOfBoundsException {
        if (row < 0 || row > this.row) {
            throw new IndexOutOfBoundsException("L'indice della riga non è valido");
        }
        if (column < 0 || column > this.column) {
            throw new IndexOutOfBoundsException("L'indice della colonna non è valido");
        }

        if (gc != null) {
            grid[row][column].insert(grid[row][column].size(), gc);
            empty = false;

            rd.insert((GeometryContainer) gc);
        }
    }

//---------------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------------
    /**
     * Una volta calcolato tutti i possibili quadrati che potrebbero essere
     * itersecati dalla geometria (nel metodo inserisci con il calcolo di x_i x_f
     * y_i y_f) il metodo interseca calcola se effettivamente la nostra geometria
     * interseca la cella passata come parametro.
     * Per calcolare le coordinate della cella partendo dalla riga e la colonna
     * della cella dobbiamo tenere conto che lo zero per openjump e quindi per i
     * file .jml è in basso a sinistra.
     */
    /**
     * Metodo che inserisce un GeometryContainer all'interno di una SpatialGrid
     * Se la geometry di gc interseca più celle, l'elemento verrà inserito in
     * tutte le celle che interseca.
     *
     * @param g oggetto Geometry da inserire
     * @exception NullPointerException se il GeometryContainer è null
     */
    void inserisci(GeometryContainer gc) throws NullPointerException {
        if (gc == null) {
            throw new NullPointerException("(SpatialGrid.inserisci)GeometryContainer null");
        }

        /**
         * Tramite le coodrinate dell'mbr che contiene la geometria ci calcoliamo tutti
         * i quatrati che interseca. Partendo dalle coordinate x_minore e x_maggiore che
         * sono rispettivamente le ascisse minori e maggiori di intersezione dell'mbr
         * con le celle della SpatialGrid controlliamo poi se effettivamente la geometria
         * intersechi tutte le celle che stanno tra x_minore x_maggiore e y_minore y_maggiore
         */
        //Creazione dell'mbr contenente la Geometry g
        Geometry mbr = gc.getMbr();
        Geometry intersezione;
        Geometry q = quad();
        intersezione = q.intersection(gc.getGeometry());
//        System.out.println("(inserisci) envelope: "+mbr.toText());
        if (intersezione != null && !intersezione.isEmpty()) {

//            checkTreshold();

            n_element ++;
            
            int[] c = trovaCelle(mbr);
            int xi = c[0], xf = c[1], yi = c[2], yf = c[3];

//         System.out.println("(inserisci) celle da controllare:\nX_i: "+x_i+", x_f: "+x_f+"\ny_i: "+y_i+", y_f: "+y_f);


//        GeometryContainer gc;
            /**
             * Doppio ciclo for che controlla se le celle comprese tra gli
             * estremi trovati sopra vengono intersecate dalla geometria
             *
             * La i ovvero la x rappresenta lo scorrimento delle colonne mentre
             * la j ovvero la y rappresenta lo scorrimento delle righe
             */
            for (int i = xi; i <= xf; i++) {
                for (int j = yi; j <= yf; j++) {

//                intersezione = interseca(j, i, width, g, sg.getWidth() * sg.getRow());
//                System.out.println("(inserisci) dentro il for");
                    intersezione = interseca(j, i, gc.getGeometry());
//                System.out.println("(inserisci) Intersezione: "+intersezione.toText());
//                System.out.println("(inserisci) Envelope Intersezione: "+intersezione.getEnvelope().toText());

                    if (intersezione != null && !intersezione.isEmpty()) {
//                    System.out.println("(inserisci) r: "+j+" ,c: "+i);
//                    gc = new GeometryContainer(indice, g);
                        insert(j, i, gc);
                    }
                }
            }
        }

    }

    /**
     * Metodo che calcola se una geometria interseca una data cella della SpatialGrid
     *
     * @param row riga della cella da controllare
     * @param column colonna della cella contraollare
     * @param sg SpatialGrid di riferimento
     * @param g Geometry da controllare
     * @return Geometry da inserire nella cella specificata
     */
    private Geometry interseca(int row, int column, Geometry g) {

        double r = row;
        double c = column;
        double w = width;
        double dim = y_f - y_i;
        double yi = y_i;
        double xi = x_i;

        //Coordinate coord[]=new Coordinate[]{new Coordinate(c*width,r*width),new Coordinate(c*width+width,r*width),new Coordinate(c*width+width,r*width+width),new Coordinate(c*width,r*width+width),new Coordinate(c*width,r*width)};

        //creazione coodinate della cella partendo dall'indice di riga e di colonna
        Coordinate A = new Coordinate(((w * c) + xi), ((dim - (r * w)) + yi));
        Coordinate B = new Coordinate((((w * c) + w) + xi), ((dim - (r * w)) + yi));
        Coordinate C = new Coordinate((((width * c) + width) + x_i), (((dim - (r * w)) - w) + yi));
        Coordinate D = new Coordinate(((w * c) + xi), (((dim - (r * w)) - w) + yi));
        Coordinate coord[] = new Coordinate[]{A, B, C, D, A};
        Geometry g1 = new GeometryFactory().createLinearRing(coord);
        Geometry quad = new GeometryFactory().createPolygon((LinearRing) g1, null);

        //creazione dell'oggetto Geometry che rappresenta l'intersezione tra la cella e g
        Geometry intersezione = quad.intersection(g);

        return intersezione;
    }

    /**
     * Metodo per trovare le celle in cui cade un mbr all'interno di una SpatialGrid
     *
     * @param g Geometry di cui trovare le dimensioni
     * @return int array posizione 0 = x_i; 1 = x_f; 2 = y_i; 3 = y_f
     */
    private int[] trovaCelle(Geometry g) {
        int[] c = new int[4];

        /**
         * Calcolo dell'x_minore x_maggiore y_minore y_maggiore
         * Il calcolo di queste coodinate viene trovando la parte intera
         * della divisione tra la coordinata di interesse e la grandezza delle
         * celle della SpatialGrid.
         * Per calcolare le y_minore e y_maggiore, siccome le coordinate presenti
         * nei file .jml utilizzano un sistema di riferimento cartesiano O(x,y),
         * abbiamo dovuto mappare il fatto che la nostra griglia strutturata come
         * matrice avesse gli indici che partono dallo [0][0] in alto a sinistra
         */
        // Trovo le caselle della SpatialGrid dove cade l'mbr
//        System.out.println("(trovaCelle) g : "+g.toText());
        Geometry mbr = g.getEnvelope();
        Coordinate[] coord = mbr.getCoordinates();
        double w = width;

//        System.out.println("(trovaCelle)Estremi celle da controllare\ncoord[0].x : "+coord[0].x);
        int x_ii = (int) (coord[0].x / w) - (int) (x_i / w);
        int x_fi = (int) (coord[2].x / w) - (int) (x_i / w);


        int y_ii = ((int) (row - 1) - ((int) (coord[0].y / w) - (int) (y_i / w)));
        int y_fi = ((int) (row - 1) - ((int) (coord[2].y / w) - (int) (y_i / w)));
//        System.out.println("(trovaCelle)Estremi celle da controllare\nx_i: "+x_ii+", x_f: "+x_fi+"\n");
//        System.out.println("(trovaCelle)Estremi celle da controllare\ny_i: "+y_ii+", y_f: "+y_fi);

        if (x_ii < 0) {
            x_ii = 0;
        }
        if (x_fi > (column - 1)) {
            x_fi = (int) (column - 1);
        }
        if (y_ii < 0) {
            y_ii = 0;
        }
        if (y_fi > (row - 1)) {
            y_fi = (int) (row - 1);
        }
        if (x_fi < 0) {
            x_fi = 0;
        }
        if (x_ii > (column - 1)) {
            x_ii = (int) (column - 1);
        }
        if (y_fi < 0) {
            y_fi = 0;
        }
        if (y_ii > (row - 1)) {
            y_ii = (int) (row - 1);
        }

        /**
         * Sezione in cui gestiamo lo scambio delle coordinate per poi inserirle
         * giuste nei cicli for
         */
        int scambio = 0;
        if (x_ii > x_fi) {
            scambio = x_ii;
            x_ii = x_fi;
            x_fi = scambio;
        }//fine if
        if (y_ii > y_fi) {
            scambio = y_ii;
            y_ii = y_fi;
            y_fi = scambio;
        }//fine if

        if (x_ii > 0) {
            x_ii--;
        }
        if (x_fi < (column - 1)) {
            x_fi++;
        }
        if (y_ii > 0) {
            y_ii--;
        }
        if (y_fi < (row - 1)) {
            y_fi++;
        }

        c[0] = x_ii;
        c[1] = x_fi;
        c[2] = y_ii;
        c[3] = y_fi;

        return c;
    }

    /**
     * Metodo che crea una Geometry grande come la SpatialGrid
     *
     * @return quad geometry che rappresenta la SpatialGrid di partenza
     */
    private Geometry quad() {
        double xi = x_i;
        double xf = x_f;
        double yi = y_i;
        double yf = y_f;

        Coordinate[] c = new Coordinate[]{new Coordinate(xi, yi), new Coordinate(xi, yf), new Coordinate(xf, yf), new Coordinate(xf, yi), new Coordinate(xi, yi)};
        Geometry g1 = new GeometryFactory().createLinearRing(c);
        Geometry quad = new GeometryFactory().createPolygon((LinearRing) g1, null);
        return quad;
    }

    /**
     * Metodo che permette di fare l'update di elemento all'interno della
     * struttura di controllo ResultDistribuiton della SpatialGrid
     *
     * @param gc Oggetto da modificare
     * @throws NullPointerException Se l'oggetto passato come parametro è null
     */
    public void updateResDist(GeometryContainer gc) throws NullPointerException {
        if (gc == null) {
            throw new NullPointerException("(SpatialGrid.inserisci)GeometryContainer null");
        }

        Geometry app = gc.getGeometry(), q = quad();
        if (app.intersects(q)) {
            rd.update(gc);
        }
    }

    public void checkTreshold() {
        if (n_element > treshold) {
            GeometryContainer gc = rd.delete(), app;

            int[] c = trovaCelle(gc.getMbr());
            int xi = c[0], xf = c[1], yi = c[2], yf = c[3];
            int cont = 0;
            ListaLinked ll;
            boolean trovato = false;

//        System.out.println("(trovaCelle)Estremi celle da controllare\nx_i: "+xi+", x_f: "+xf+"\n");
//        System.out.println("(trovaCelle)Estremi celle da controllare\ny_i: "+yi+", y_f: "+yf);


            for (int i = xi; i <= xf; i++) {
                for (int j = yi; j <= yf; j++) {
                    ll = grid[j][i];

                    if (!ll.isEmpty()) {
                        cont = 0;

                        while (cont < ll.size() && !trovato) {
                            app = (GeometryContainer) ll.get(cont);
                            if (gc.getId() == app.getId()) {
                                ll.remove(cont);
                                trovato = true;
                            }
                            cont ++;
                        }
                        grid[j][i] = ll;
                        trovato = false;
                    }
                }
            }
            n_element--;
        }
    }

//---------------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------------
    /**
     * Sostituisce la ListaLinked in posizione r, c.
     * Se la nuova ListaLinked è uguale a null lascia la vecchia
     *
     * @param r Riga in cui inserire
     * @param c Colonna in cui inserire
     * @param ln ListaLinked da inserire
     * @throws IndexOutOfBoundsException Se row o column non sono validi
     * @throws NullPointerException Se la ListaLinked è null
     */
    public void insertLL(int row, int column, ListaLinked ln) throws IndexOutOfBoundsException, NullPointerException {
        if (row < 0 || row > this.row) {
            throw new IndexOutOfBoundsException("L'indice della riga non è valido");
        }
        if (column < 0 || column > this.column) {
            throw new IndexOutOfBoundsException("L'indice della colonna non è valido");
        }
        if (ln == null) {
            throw new IndexOutOfBoundsException("La ListaLinked è null");
        }

        grid[row][column] = ln;
    }

    /**
     * Metodo che restituisce la ListaLinked associata alla cell
     * a
     * @param row
     * @param column
     * @return ListaLinked
     * @exception IndexOutOfBoundsException Se row o column non sono validi
     */
    public ListaLinked getLL(int row, int column) throws IndexOutOfBoundsException {
        if (row < 0 || row > this.row) {
            throw new IndexOutOfBoundsException("L'indice della riga non è valido");
        }
        if (column < 0 || column > this.column) {
            throw new IndexOutOfBoundsException("L'indice della colonna non è valido");
        }
        return grid[row][column];
    }

    /**
     * 
     * @return numero di righe della SpatialGrid
     */
    public double getRow() {
        return row;
    }

    /**
     * 
     * @return numero di colonne della SpatialGrid
     */
    public double getColumn() {
        return column;
    }

    /**
     * Ritorna la dimensione interna di ogni cella della matrice
     * @return Double la dimensione interna di ogni cella della matrice
     */
    public double getWidth() {
        return width;
    }

    /**
     *
     * @return Valore x_f
     */
    public double getX_f() {
        return x_f;
    }

    /**
     *
     * @return Valore x_i
     */
    public double getX_i() {
        return x_i;
    }

    /**
     *
     * @return Valore y_f
     */
    public double getY_f() {
        return y_f;
    }

    /**
     *
     * @return Valore y_i
     */
    public double getY_i() {
        return y_i;
    }

    /**
     *
     * @return boolean true if the SpatialGrid is empty, else false
     */
    public boolean isEmpty() {
        return empty;
    }

    public void getDimension() {
        System.out.println("SpatialGrid dimension:\nX_i: " + x_i + ", x_f: " + x_f + "\ny_i: " + y_i + ", y_f: " + y_f);
    }

    /**
     * Metodo che stampa tutto il contenuto di una cella
     *
     * @param row
     * @param column
     * @throws IndexOutOfBoundsException se gli indice non rientrano nelle dimensioni
     * della SpatialGrid
     */
    public void toText(int row, int column) throws IndexOutOfBoundsException {
        if (row < 0 || row > this.row) {
            throw new IndexOutOfBoundsException("L'indice della riga non è valido");
        }
        if (column < 0 || column > this.column) {
            throw new IndexOutOfBoundsException("L'indice della colonna non è valido");
        }

        ListaLinked ll;
        GeometryContainer gc;
        Geometry g;
        ll = (ListaLinked) this.getLL(row, column);

        if (ll.size() == 0) {
            System.out.println(" empty");
        } else {
            for (int i = 0; i < ll.size(); i++) {
                gc = (GeometryContainer) ll.get(i);
                g = gc.getGeometry();
//                System.out.println(" id "+gc.getId()+": n_intersezioni = "+gc.getN_risultati()+" : "+g.toText());
                System.out.println(" id " + gc.getId() + ": n_intersezioni = " + gc.getN_risultati());
            }//fine for
        }

    }

    /**
     * Metodo che stampa l'intero contenuto della SpatialGrid
     * Questo metodo scorre tutte le celle della SpatialGrid e richiama il
     * metodo toText() per stamparne il contenuto
     */
    public void stampaGrid() {

        System.out.println("\nStampa della Grid in corso:");
        System.out.println("(righe,colonne)--> contenuto della cella");
        System.out.println("...................................");
        for (int i = 0; i < row; i++) {
            for (int j = 0; j < column; j++) {
                System.out.print("(" + i + "," + j + ")-->");
                toText(i, j);
            }//fine for j
        }//fine for i
        System.out.println("...................................\n");
    }

    /**
     * Metodo che stampa il contenuto della struttura di controllo
     * ResultDistribution
     */
    public void stampaResDist() {
        rd.printResultDistribution();
    }
}//end class util

